#include "reader.h"
#include "value.h"
#include <utility>
#include <cstdio>
#include <cassert>
#include <cstring>
#include <iostream>
#include <stdexcept>

#if _MSC_VER >= 1400 // VC++ 8.0
#pragma warning( disable : 4996 )   // disable warning about strdup being deprecated.
#endif

namespace Json {

    // Implementation of class Features
    // ////////////////////////////////

    Features::Features()
    : allowComments_( true )
    , strictRoot_( false )
    {
    }

    Features
    Features::all()
    {
        return Features();
    }

    Features
    Features::strictMode()
    {
        Features features;
        features.allowComments_ = false;
        features.strictRoot_ = true;
        return features;
    }

    // Implementation of class Reader
    // ////////////////////////////////

    static inline bool
    in( Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4 )
    {
        return c == c1  ||  c == c2  ||  c == c3  ||  c == c4;
    }

    static inline bool
    in( Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4, Reader::Char c5 )
    {
        return c == c1  ||  c == c2  ||  c == c3  ||  c == c4  ||  c == c5;
    }

    static bool
    containsNewLine( Reader::Location begin,
            Reader::Location end )
    {
        for ( ; begin < end; ++begin )
            if ( *begin == '\n'  ||  *begin == '\r' )
                return true;
        return false;
    }

    static std::string
    codePointToUTF8(unsigned int cp)
    {
        std::string result;

        // based on description from http://en.wikipedia.org/wiki/UTF-8

        if (cp <= 0x7f)
        {
            result.resize(1);
            result[0] = static_cast<char> (cp);
        }
        else if (cp <= 0x7FF)
        {
            result.resize(2);
            result[1] = static_cast<char> (0x80 | (0x3f & cp));
            result[0] = static_cast<char> (0xC0 | (0x1f & (cp >> 6)));
        }
        else if (cp <= 0xFFFF)
        {
            result.resize(3);
            result[2] = static_cast<char> (0x80 | (0x3f & cp));
            result[1] = 0x80 | static_cast<char> ((0x3f & (cp >> 6)));
            result[0] = 0xE0 | static_cast<char> ((0xf & (cp >> 12)));
        }
        else if (cp <= 0x10FFFF)
        {
            result.resize(4);
            result[3] = static_cast<char> (0x80 | (0x3f & cp));
            result[2] = static_cast<char> (0x80 | (0x3f & (cp >> 6)));
            result[1] = static_cast<char> (0x80 | (0x3f & (cp >> 12)));
            result[0] = static_cast<char> (0xF0 | (0x7 & (cp >> 18)));
        }

        return result;
    }


    // Class Reader
    // //////////////////////////////////////////////////////////////////

    Reader::Reader()
    : features_( Features::all() )
    {
    }

    Reader::Reader( const Features &features )
    : features_( features )
    {
    }

    bool
    Reader::parse( const std::string &document,
            Value &root,
            bool collectComments )
    {
        document_ = document;
        const char *begin = document_.c_str();
        const char *end = begin + document_.length();
        return parse( begin, end, root, collectComments );
    }

    bool
    Reader::parse( std::istream& sin,
            Value &root,
            bool collectComments )
    {
        //std::istream_iterator<char> begin(sin);
        //std::istream_iterator<char> end;
        // Those would allow streamed input from a file, if parse() were a
        // template function.

        // Since std::string is reference-counted, this at least does not
        // create an extra copy.
        std::string doc;
        std::getline(sin, doc, (char) EOF);
        return parse( doc, root, collectComments );
    }

    bool
    Reader::parse( const char *beginDoc, const char *endDoc,
            Value &root,
            bool collectComments )
    {
        if ( !features_.allowComments_ )
        {
            collectComments = false;
        }

        begin_ = beginDoc;
        end_ = endDoc;
        collectComments_ = collectComments;
        current_ = begin_;
        lastValueEnd_ = 0;
        lastValue_ = 0;
        commentsBefore_ = "";
        errors_.clear();
        while ( !nodes_.empty() )
            nodes_.pop();
        nodes_.push( &root );

        bool successful = readValue();
        Token token;
        skipCommentTokens( token );
        if ( collectComments_  &&  !commentsBefore_.empty() )
            root.setComment( commentsBefore_, commentAfter );
        if ( features_.strictRoot_ )
        {
            if ( !root.isArray()  &&  !root.isObject() )
            {
                // Set error location to start of doc, ideally should be first token found in doc
                token.type_ = tokenError;
                token.start_ = beginDoc;
                token.end_ = endDoc;
                addError( "A valid JSON document must be either an array or an object value.",
                        token );
                return false;
            }
        }
        return successful;
    }

    bool
    Reader::readValue()
    {
        Token token;
        skipCommentTokens( token );
        bool successful = true;

        if ( collectComments_  &&  !commentsBefore_.empty() )
        {
            currentValue().setComment( commentsBefore_, commentBefore );
            commentsBefore_ = "";
        }


        switch ( token.type_ )
        {
            case tokenObjectBegin:
                successful = readObject( token );
                break;
            case tokenArrayBegin:
                successful = readArray( token );
                break;
            case tokenNumber:
                successful = decodeNumber( token );
                break;
            case tokenString:
                successful = decodeString( token );
                break;
            case tokenTrue:
                currentValue() = true;
                break;
            case tokenFalse:
                currentValue() = false;
                break;
            case tokenNull:
                currentValue() = Value();
                break;
            default:
                return addError( "Syntax error: value, object or array expected.", token );
        }

        if ( collectComments_ )
        {
            lastValueEnd_ = current_;
            lastValue_ = &currentValue();
        }

        return successful;
    }

    void
    Reader::skipCommentTokens( Token &token )
    {
        if ( features_.allowComments_ )
        {
            do
            {
                readToken( token );
            }
            while ( token.type_ == tokenComment );
        }
        else
        {
            readToken( token );
        }
    }

    bool
    Reader::expectToken( TokenType type, Token &token, const char *message )
    {
        readToken( token );
        if ( token.type_ != type )
            return addError( message, token );
        return true;
    }

    bool
    Reader::readToken( Token &token )
    {
        skipSpaces();
        token.start_ = current_;
        Char c = getNextChar();
        bool ok = true;
        switch ( c )
        {
            case '{':
                token.type_ = tokenObjectBegin;
                break;
            case '}':
                token.type_ = tokenObjectEnd;
                break;
            case '[':
                token.type_ = tokenArrayBegin;
                break;
            case ']':
                token.type_ = tokenArrayEnd;
                break;
            case '"':
                token.type_ = tokenString;
                ok = readString();
                break;
            case '/':
                token.type_ = tokenComment;
                ok = readComment();
                break;
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            case '-':
                token.type_ = tokenNumber;
                readNumber();
                break;
            case 't':
                token.type_ = tokenTrue;
                ok = match( "rue", 3 );
                break;
            case 'f':
                token.type_ = tokenFalse;
                ok = match( "alse", 4 );
                break;
            case 'n':
                token.type_ = tokenNull;
                ok = match( "ull", 3 );
                break;
            case ',':
                token.type_ = tokenArraySeparator;
                break;
            case ':':
                token.type_ = tokenMemberSeparator;
                break;
            case 0:
                token.type_ = tokenEndOfStream;
                break;
            default:
                ok = false;
                break;
        }
        if ( !ok )
            token.type_ = tokenError;
        token.end_ = current_;
        return true;
    }

    void
    Reader::skipSpaces()
    {
        while ( current_ != end_ )
        {
            Char c = *current_;
            if ( c == ' '  ||  c == '\t'  ||  c == '\r'  ||  c == '\n' )
                ++current_;
            else
                break;
        }
    }

    bool
    Reader::match( Location pattern,
            int patternLength )
    {
        if ( end_ - current_ < patternLength )
            return false;
        int index = patternLength;
        while ( index-- )
            if ( current_[index] != pattern[index] )
                return false;
        current_ += patternLength;
        return true;
    }

    bool
    Reader::readComment()
    {
        Location commentBegin = current_ - 1;
        Char c = getNextChar();
        bool successful = false;
        if ( c == '*' )
            successful = readCStyleComment();
        else if ( c == '/' )
            successful = readCppStyleComment();
        if ( !successful )
            return false;

        if ( collectComments_ )
        {
            CommentPlacement placement = commentBefore;
            if ( lastValueEnd_  &&  !containsNewLine( lastValueEnd_, commentBegin ) )
            {
                if ( c != '*'  ||  !containsNewLine( commentBegin, current_ ) )
                    placement = commentAfterOnSameLine;
            }

            addComment( commentBegin, current_, placement );
        }
        return true;
    }

    void
    Reader::addComment( Location begin,
            Location end,
            CommentPlacement placement )
    {
        assert( collectComments_ );
        if ( placement == commentAfterOnSameLine )
        {
            assert( lastValue_ != 0 );
            lastValue_->setComment( std::string( begin, end ), placement );
        }
        else
        {
            if ( !commentsBefore_.empty() )
                commentsBefore_ += "\n";
            commentsBefore_ += std::string( begin, end );
        }
    }

    bool
    Reader::readCStyleComment()
    {
        while ( current_ != end_ )
        {
            Char c = getNextChar();
            if ( c == '*'  &&  *current_ == '/' )
                break;
        }
        return getNextChar() == '/';
    }

    bool
    Reader::readCppStyleComment()
    {
        while ( current_ != end_ )
        {
            Char c = getNextChar();
            if (  c == '\r'  ||  c == '\n' )
                break;
        }
        return true;
    }

    void
    Reader::readNumber()
    {
        while ( current_ != end_ )
        {
            if ( !(*current_ >= '0'  &&  *current_ <= '9')  &&
                    !in( *current_, '.', 'e', 'E', '+', '-' ) )
                break;
            ++current_;
        }
    }

    bool
    Reader::readString()
    {
        Char c = 0;
        while ( current_ != end_ )
        {
            c = getNextChar();
            if ( c == '\\' )
                getNextChar();
            else if ( c == '"' )
                break;
        }
        return c == '"';
    }

    bool
    Reader::readObject( Token &tokenStart )
    {
        Token tokenName;
        std::string name;
        currentValue() = Value( objectValue );
        while ( readToken( tokenName ) )
        {
            bool initialTokenOk = true;
            while ( tokenName.type_ == tokenComment  &&  initialTokenOk )
                initialTokenOk = readToken( tokenName );
            if  ( !initialTokenOk )
                break;
            if ( tokenName.type_ == tokenObjectEnd  &&  name.empty() )  // empty object
                return true;
            if ( tokenName.type_ != tokenString )
                break;

            name = "";
            if ( !decodeString( tokenName, name ) )
                return recoverFromError( tokenObjectEnd );

            Token colon;
            if ( !readToken( colon ) ||  colon.type_ != tokenMemberSeparator )
            {
                return addErrorAndRecover( "Missing ':' after object member name",
                        colon,
                        tokenObjectEnd );
            }
            Value &value = currentValue()[ name ];
            nodes_.push( &value );
            bool ok = readValue();
            nodes_.pop();
            if ( !ok ) // error already set
                return recoverFromError( tokenObjectEnd );

            Token comma;
            if ( !readToken( comma )
                    ||  ( comma.type_ != tokenObjectEnd  &&
                    comma.type_ != tokenArraySeparator &&
                    comma.type_ != tokenComment ) )
            {
                return addErrorAndRecover( "Missing ',' or '}' in object declaration",
                        comma,
                        tokenObjectEnd );
            }
            bool finalizeTokenOk = true;
            while ( comma.type_ == tokenComment &&
                    finalizeTokenOk )
                finalizeTokenOk = readToken( comma );
            if ( comma.type_ == tokenObjectEnd )
                return true;
        }
        return addErrorAndRecover( "Missing '}' or object member name",
                tokenName,
                tokenObjectEnd );
    }

    bool
    Reader::readArray( Token &tokenStart )
    {
        currentValue() = Value( arrayValue );
        skipSpaces();
        if ( *current_ == ']' ) // empty array
        {
            Token endArray;
            readToken( endArray );
            return true;
        }
        int index = 0;
        while ( true )
        {
            Value &value = currentValue()[ index++ ];
            nodes_.push( &value );
            bool ok = readValue();
            nodes_.pop();
            if ( !ok ) // error already set
                return recoverFromError( tokenArrayEnd );

            Token token;
            // Accept Comment after last item in the array.
            ok = readToken( token );
            while ( token.type_ == tokenComment  &&  ok )
            {
                ok = readToken( token );
            }
            bool badTokenType = ( token.type_ == tokenArraySeparator  &&
                    token.type_ == tokenArrayEnd );
            if ( !ok  ||  badTokenType )
            {
                return addErrorAndRecover( "Missing ',' or ']' in array declaration",
                        token,
                        tokenArrayEnd );
            }
            if ( token.type_ == tokenArrayEnd )
                break;
        }
        return true;
    }

    bool
    Reader::decodeNumber( Token &token )
    {
        bool isDouble = false;
        for ( Location inspect = token.start_; inspect != token.end_; ++inspect )
        {
            isDouble = isDouble
                    ||  in( *inspect, '.', 'e', 'E', '+' )
                    ||  ( *inspect == '-'  &&  inspect != token.start_ );
        }
        if ( isDouble )
            return decodeDouble( token );
        Location current = token.start_;
        bool isNegative = *current == '-';
        if ( isNegative )
            ++current;
        Value::LongInt threshold = (Value::LongInt)(isNegative ? -Value::minLInt
                : Value::maxLInt) / 10;
        Value::LongInt value = 0;
        while ( current < token.end_ )
        {
            Char c = *current++;
            if ( c < '0'  ||  c > '9' )
                return addError( "'" + std::string( token.start_, token.end_ ) + "' is not a number.", token );
            if ( value >= threshold )
                return decodeDouble( token );
            value = value * 10 + Value::UInt(c - '0');
        }
        if (isNegative)
        {
            value = -Value::LongInt(value);
        }
        if (value <= Value::LongInt(Value::maxInt) && value >= Value::LongInt(Value::minInt))
        {
            currentValue() = Value::Int(value);
        }
        else
        {
            currentValue() = value;
        }
        //        if ( isNegative )
        //            currentValue() = -Value::Int( value );
        //        else if ( value <= Value::UInt(Value::maxInt) )
        //            currentValue() = Value::Int( value );
        //        else
        //            currentValue() = value;
        return true;
    }

    bool
    Reader::decodeDouble( Token &token )
    {
        double value = 0;
        const int bufferSize = 32;
        int count;
        int length = int(token.end_ - token.start_);
        if ( length < bufferSize )
        {
            Char buffer[bufferSize];
            memcpy( buffer, token.start_, length );
            buffer[length] = 0;
            count = sscanf( buffer, "%lf", &value );
        }
        else
        {
            std::string buffer( token.start_, token.end_ );
            count = sscanf( buffer.c_str(), "%lf", &value );
        }

        if ( count != 1 )
            return addError( "'" + std::string( token.start_, token.end_ ) + "' is not a number.", token );
        currentValue() = value;
        return true;
    }

    bool
    Reader::decodeString( Token &token )
    {
        std::string decoded;
        if ( !decodeString( token, decoded ) )
            return false;
        currentValue() = decoded;
        return true;
    }

    bool
    Reader::decodeString( Token &token, std::string &decoded )
    {
        decoded.reserve( token.end_ - token.start_ - 2 );
        Location current = token.start_ + 1; // skip '"'
        Location end = token.end_ - 1;      // do not include '"'
        while ( current != end )
        {
            Char c = *current++;
            if ( c == '"' )
                break;
            else if ( c == '\\' )
            {
                if ( current == end )
                    return addError( "Empty escape sequence in string", token, current );
                Char escape = *current++;
                switch ( escape )
                {
                    case '"': decoded += '"';
                        break;
                    case '/': decoded += '/';
                        break;
                    case '\\': decoded += '\\';
                        break;
                    case 'b': decoded += '\b';
                        break;
                    case 'f': decoded += '\f';
                        break;
                    case 'n': decoded += '\n';
                        break;
                    case 'r': decoded += '\r';
                        break;
                    case 't': decoded += '\t';
                        break;
                    case 'u':
                    {
                        unsigned int unicode;
                        if ( !decodeUnicodeCodePoint( token, current, end, unicode ) )
                            return false;
                        decoded += codePointToUTF8(unicode);
                    }
                        break;
                    default:
                        return addError( "Bad escape sequence in string", token, current );
                }
            }
            else
            {
                decoded += c;
            }
        }
        return true;
    }

    bool
    Reader::decodeUnicodeCodePoint( Token &token,
            Location &current,
            Location end,
            unsigned int &unicode )
    {

        if ( !decodeUnicodeEscapeSequence( token, current, end, unicode ) )
            return false;
        if (unicode >= 0xD800 && unicode <= 0xDBFF)
        {
            // surrogate pairs
            if (end - current < 6)
                return addError( "additional six characters expected to parse unicode surrogate pair.", token, current );
            unsigned int surrogatePair;
            if (*(current++) == '\\' && *(current++) == 'u')
            {
                if (decodeUnicodeEscapeSequence( token, current, end, surrogatePair ))
                {
                    unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF);
                }
                else
                    return false;
            }
            else
                return addError( "expecting another \\u token to begin the second half of a unicode surrogate pair", token, current );
        }
        return true;
    }

    bool
    Reader::decodeUnicodeEscapeSequence( Token &token,
            Location &current,
            Location end,
            unsigned int &unicode )
    {
        if ( end - current < 4 )
            return addError( "Bad unicode escape sequence in string: four digits expected.", token, current );
        unicode = 0;
        for ( int index = 0; index < 4; ++index )
        {
            Char c = *current++;
            unicode *= 16;
            if ( c >= '0'  &&  c <= '9' )
                unicode += c - '0';
            else if ( c >= 'a'  &&  c <= 'f' )
                unicode += c - 'a' + 10;
            else if ( c >= 'A'  &&  c <= 'F' )
                unicode += c - 'A' + 10;
            else
                return addError( "Bad unicode escape sequence in string: hexadecimal digit expected.", token, current );
        }
        return true;
    }

    bool
    Reader::addError( const std::string &message,
            Token &token,
            Location extra )
    {
        ErrorInfo info;
        info.token_ = token;
        info.message_ = message;
        info.extra_ = extra;
        errors_.push_back( info );
        return false;
    }

    bool
    Reader::recoverFromError( TokenType skipUntilToken )
    {
        int errorCount = int(errors_.size());
        Token skip;
        while ( true )
        {
            if ( !readToken(skip) )
                errors_.resize( errorCount ); // discard errors caused by recovery
            if ( skip.type_ == skipUntilToken  ||  skip.type_ == tokenEndOfStream )
                break;
        }
        errors_.resize( errorCount );
        return false;
    }

    bool
    Reader::addErrorAndRecover( const std::string &message,
            Token &token,
            TokenType skipUntilToken )
    {
        addError( message, token );
        return recoverFromError( skipUntilToken );
    }

    Value &
    Reader::currentValue()
    {
        return *(nodes_.top());
    }

    Reader::Char
    Reader::getNextChar()
    {
        if ( current_ == end_ )
            return 0;
        return *current_++;
    }

    void
    Reader::getLocationLineAndColumn( Location location,
            int &line,
            int &column ) const
    {
        Location current = begin_;
        Location lastLineStart = current;
        line = 0;
        while ( current < location  &&  current != end_ )
        {
            Char c = *current++;
            if ( c == '\r' )
            {
                if ( *current == '\n' )
                    ++current;
                lastLineStart = current;
                ++line;
            }
            else if ( c == '\n' )
            {
                lastLineStart = current;
                ++line;
            }
        }
        // column & line start at 1
        column = int(location - lastLineStart) + 1;
        ++line;
    }

    std::string
    Reader::getLocationLineAndColumn( Location location ) const
    {
        int line, column;
        getLocationLineAndColumn( location, line, column );
        char buffer[18 + 16 + 16 + 1];
        sprintf( buffer, "Line %d, Column %d", line, column );
        return buffer;
    }

    std::string
    Reader::getFormatedErrorMessages() const
    {
        std::string formattedMessage;
        for ( Errors::const_iterator itError = errors_.begin();
                itError != errors_.end();
                ++itError )
        {
            const ErrorInfo &error = *itError;
            formattedMessage += "* " + getLocationLineAndColumn( error.token_.start_ ) + "\n";
            formattedMessage += "  " + error.message_ + "\n";
            if ( error.extra_ )
                formattedMessage += "See " + getLocationLineAndColumn( error.extra_ ) + " for detail.\n";
        }
        return formattedMessage;
    }

    std::istream& operator>>( std::istream &sin, Value &root )
    {
        Json::Reader reader;
        bool ok = reader.parse(sin, root, true);
        //JSON_ASSERT( ok );
        if (!ok) throw std::runtime_error(reader.getFormatedErrorMessages());
        return sin;
    }


} // namespace Json
